#!/usr/bin/perl

######################################################################
package Swignition::GenericParser::RDFa;
######################################################################

use Swignition::MagicString;
use Swignition::Namespace;
use XML::LibXML qw(:all);
use strict;

sub is_enabled
{
	my $this = shift;

	if ($this->{options}->{p_rdfa})
	{
		if ($this->{options}->{rdfa_strict_doctype})
		{
			my $src = $this->{_raw_source} || $this->{Source};
			return
				if ($src !~ m| <!DOCTYPE \s+ html \s+ PUBLIC \s+
					"-//W3C//DTD\sXHTML\+RDFa\s1\.0//EN"|six);
		}
		if ($this->{options}->{rdfa_strict_version})
		{
			return
				if ($this->{DOM}->documentElement->getAttribute('version') !~ /rdfa/i);
		}
	
		return 1;
	}
	
	return 0;
}

sub consume
# http://www.w3.org/TR/rdfa-syntax/#sec_5.5.
{
	my $this = shift;
	my $dom  = shift || $this->{DOM}->documentElement;
	
	return 0 unless ($dom->nodeType == XML_ELEMENT_NODE);
	
	# At the beginning of processing, an initial [evaluation context] is created
	my $base               = shift || $this->uri;
	my $parent_subject     = shift || $base;
	my $parent_object      = shift || undef;
	my $uri_mappings       = shift || {};
	my $incomplete_triples = shift || ();
	my $language           = shift || undef;
	
	# Processing begins by applying the processing rules below to the document
	# object, in the context of this initial [evaluation context]. All elements
	# in the tree are also processed according to the rules described below,
	# depth-first, although the [evaluation context] used for each set of rules
	# will be based on previous rules that may have been applied.

	my $current_element = $dom;

	# First, the local values are initialized
	my $recurse            = 1;
	my $skip_element       = 0;
	my $new_subject        = undef;
	my $current_object_resource = undef;
	my $local_uri_mappings = $uri_mappings;
	my $local_incomplete_triples = ();
	my $current_language   = $language;
	
	my $activity = 0;
	
	# Next the [current element] is parsed for [URI mapping]s and these are
	# added to the [local list of URI mappings]. Note that a [URI mapping] 
	# will simply overwrite any current mapping in the list that has the same
	# name
	foreach my $A ($current_element->getAttributes)
	{
		my $attr = $A->getName;
		my $val  = $A->getValue;
		
		if ($attr =~ /^xmlns\:(.*)$/i)
		{
			$local_uri_mappings->{$1} = $val;
			$this->mergeNS($1, $val, $1, $current_element->getAttribute('_xpath'), 0);
		}
	}
	# Mappings are provided by @xmlns. The value to be mapped is set by
	# the XML namespace prefix, and the value to map is the value of the
	# attribute - a URI. Note that the URI is not processed in any way;
	# in particular if it is a relative path it is not resolved against
	# the current [base]. Authors are advised to follow best practice
	# for using namespaces, which includes not using relative paths.
	
	# EXTENSION
	# The rdfa.info wiki includes an additional proposed syntax for defining
	# prefixes. This uses a single @prefix attribute, making it more DTD
	# friendly. Let's support that too.
	if ($current_element->getAttribute('prefix'))
	{
		foreach my $mapping ((split /\s+/, $current_element->getAttribute('prefix')))
		{
			$this->mergeNS($1, $2, $1, $current_element->getAttribute('_xpath'), 0)
				if ($mapping =~ /^([^=]+)=(.+)$/);
		}
	}
	
	# The [current element] is also parsed for any language information, and
	# if present, [current language] is set accordingly.
	# Language information can be provided using the general-purpose XML
	# attribute @xml:lang .
	if (defined $current_element->getAttributeNode('xml:lang'))
		{ $current_language = $current_element->getAttribute('xml:lang'); }
	elsif (defined $current_element->getAttributeNode('lang'))
		{ $current_language = $current_element->getAttribute('lang'); }
	elsif (defined $current_element->getAttributeNode('_xmllang'))
		{ $current_language = $current_element->getAttribute('_xmllang'); }
		
	# If the [current element] contains no valid @rel or @rev URI, obtained
	# according to the section on CURIE and URI Processing, then the next step 
	# is to establish a value for [new subject]. Any of the attributes that 
	# can carry a resource can set [new subject]
	my $rel = $current_element->getAttribute('rel');
	$rel =~ s/(^\s+|\s+$)//g;
	$rel =~ s/\s+/ /g;
	my @rel = split / /, $rel;
	my @REL;
	foreach my $r (@rel)
	{
		my $R = reservedWordOrCurie($this, $r, $current_element);
		push @REL, $R if (length $R);
	}
	my $rev = $current_element->getAttribute('rev');
	$rev =~ s/(^\s+|\s+$)//g;
	$rev =~ s/\s+/ /g;
	my @rev = split / /, $rev;
	my @REV;
	foreach my $r (@rev)
	{
		my $R = reservedWordOrCurie($this, $r, $current_element);
		push @REV, $R if (length $R);
	}
	if ((!defined $current_element->getAttribute('rel'))
	&&  (!defined $current_element->getAttribute('rev')))
	{
		# [new subject] is set to the URI obtained from the first match
		# from the following rules:
		
		# by using the URI from @about, if present, obtained according to
		# the section on CURIE and URI Processing ; 
		if (defined $current_element->getAttributeNode('about'))
			{ $new_subject = uriOrSafeCurie($this, $current_element->getAttribute('about'), $current_element); }
			
		# otherwise, by using the URI from @src, if present, obtained
		# according to the section on CURIE and URI Processing.
		elsif (defined $current_element->getAttributeNode('src'))
			{ $new_subject = uriOrSafeCurie($this, $current_element->getAttribute('src'), $current_element); }
			
		# otherwise , by using the URI from @resource, if present, obtained
		# according to the section on CURIE and URI Processing ; 
		elsif (defined $current_element->getAttributeNode('resource'))
			{ $new_subject = uriOrSafeCurie($this, $current_element->getAttribute('resource'), $current_element); }
			
		# otherwise , by using the URI from @href, if present, obtained
		# according to the section on CURIE and URI Processing.
		elsif (defined $current_element->getAttributeNode('href'))
			{ $new_subject = uriOrSafeCurie($this, $current_element->getAttribute('href'), $current_element); }
			
		# If no URI is provided by a resource attribute, then the first
		# match from the following rules will apply: 
		else
		{
			# if the element is the head or body element then act as if
			# there is an empty @about present, and process it according to
			# the rule for @about, above; 
			if ($current_element->tagName eq 'head' || $current_element->tagName eq 'body')
			{
				$new_subject = $this->uri;
			}
			
			# if @instanceof is present, obtained according to the section
			# on CURIE and URI Processing , then [new subject] is set to be
			# a newly created [bnode]; 
			elsif (defined $current_element->getAttributeNode('typeof')
				||  defined $current_element->getAttributeNode('instanceof'))
			{
				if (length $current_element->getAttribute('id'))
					{ $new_subject = Swignition::uF::TDBURI($this->uri.'#'.$current_element->getAttribute('id')); }
				else
					{ $new_subject = Swignition::GenericParser::Utils::BNodeURI($current_element); }
			}
			
			# otherwise, if [parent object] is present, [new subject] is set
			# to the value of [parent object]. Additionally if @property is not
			# present then the [skip element] flag is set to 'true'; 
			elsif ($parent_object)
			{
				$new_subject = $parent_object;
				$skip_element = 1
					unless (defined $current_element->getAttributeNode('property'));
			}
		}
	}
	
	# If the [current element] does contain a valid @rel or @rev URI, obtained 
	# according to the section on CURIE and URI Processing, then the next step 
	# is to establish both a value for [new subject] and a value for [current
	# object resource]:
	else
	{
		# [new subject] is set to the URI obtained from the first match
		# from the following rules: 
		
		# by using the URI from @about, if present, obtained according
		# to the section on CURIE and URI Processing; 
		if (defined $current_element->getAttributeNode('about'))
			{ $new_subject = uriOrSafeCurie($this, $current_element->getAttribute('about'), $current_element); }
			
		# otherwise, by using the URI from @src, if present, obtained
		# according to the section on CURIE and URI Processing.
		elsif (defined $current_element->getAttributeNode('src'))
			{ $new_subject = uriOrSafeCurie($this, $current_element->getAttribute('src'), $current_element); }

		# If no URI is provided then the first match from the following rules
		# will apply: 
		else
		{
			# if the element is the head or body element then act as if
			# there is an empty @about present, and process it according
			# to the rule for @about, above; 
			if ($current_element->tagName eq 'head' || $current_element->tagName eq 'body')
			{
				$new_subject = $this->uri;
			}
			
			# if @instanceof is present, obtained according to the section
			# on CURIE and URI Processing, then [new subject] is set to be a
			# newly created [bnode]; 
			elsif (defined $current_element->getAttributeNode('typeof')
				||  defined $current_element->getAttributeNode('instanceof'))
			{
				if (length $current_element->getAttribute('id'))
					{ $new_subject = Swignition::uF::TDBURI($this->uri.'#'.$current_element->getAttribute('id')); }
				else
					{ $new_subject = Swignition::GenericParser::Utils::BNodeURI($current_element); }
			}
			
			# otherwise, if [parent object] is present, [new subject] is set 
			# to that. 
			elsif ($parent_object)
			{
				$new_subject = $parent_object;
			}
		}
		
		# Then the [current object resource] is set to the URI obtained
		# from the first match from the following rules: 
		
		# by using the URI from @resource, if present, obtained according to
		# the section on CURIE and URI Processing; 
		if (defined $current_element->getAttributeNode('resource'))
			{ $current_object_resource = uriOrSafeCurie($this, $current_element->getAttribute('resource'), $current_element); }
			
		# otherwise, by using the URI from @href, if present, obtained according
		# to the section on CURIE and URI Processing.
		elsif (defined $current_element->getAttributeNode('href'))
			{ $current_object_resource = uriOrSafeCurie($this, $current_element->getAttribute('href'), $current_element); }
			
		# Note that final value of the [current object resource] will either
		# be null (from initialization), a full URI or a bnode. 
	}
	
	# If in any of the previous steps a [new subject] was set to a non-null
	# value, it is now used to provide a subject for type values
	if ($new_subject
	&& defined ($current_element->getAttributeNode('instanceof')
		.$current_element->getAttributeNode('typeof')))
	{
		my $SUBJECT = Swignition::RDFModel::Subject->new($new_subject);
		
		# One or more 'types' for the [ new subject ] can be set by using
		# @instanceof. If present, the attribute must contain one or more
		# URIs, obtained according to the section on URI and CURIE Processing...
	
		my $instanceof = $current_element->getAttribute('typeof');
		$instanceof = $current_element->getAttribute('instanceof')
			unless defined($instanceof);
		$instanceof =~ s/\s+/ /g;
		$instanceof =~ s/(^\s|\s$)//g;
		my @instanceof = split / /, $instanceof;
		
		foreach my $instanceof (@instanceof)
		{
			my $rdftype = curie($this, $instanceof, $current_element);
			next unless $rdftype;
		
			# ... each of which is used to generate a triple as follows:
			#
			# subject
			#     [new subject] 
			# predicate
	    	#     http://www.w3.org/1999/02/22-rdf-syntax-ns#type 
			# object
			#     full URI of 'type' 

			my $VALUE   = Swignition::RDFModel::Value->new($rdftype, 1);
			$SUBJECT->addVal('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', $VALUE);
			$activity++;
		}

		$this->mergeSubject($SUBJECT);
			
		# Note that none of this block is executed if there is no [new subject]
		# value, i.e., [new subject] remains null. 
	}
	
	# If in any of the previous steps a [current object resource] was set to
	# a non-null value, it is now used to generate triples
	if ($current_object_resource)
	{
		# Predicates for the [ current object resource ] can be set b
		# using one or both of the @rel and @rev attributes:
		#
		#    * If present, @rel will contain one or more URIs, obtained
		#      according to the section on CURIE and URI Processing each
		#      of which is used to generate a triple as follows:
		#
		#      subject
		#          [new subject] 
		#      predicate
		#          full URI 
		#      object
		#          [current object resource] 
		
		my $SUBJECT = Swignition::RDFModel::Subject->new($new_subject);
		foreach my $r (@REL)
		{
			my $VALUE   = Swignition::RDFModel::Value->new($current_object_resource, 1);
			$SUBJECT->addVal($r, $VALUE);
		}
		$this->mergeSubject($SUBJECT);
		$activity++;

		#    * If present, @rev will contain one or more URIs, obtained
		#      according to the section on CURIE and URI Processing each
		#      of which is used to generate a triple as follows:
		#
		#      subject
		#          [current object resource] 
		#      predicate
		#          full URI 
		#      object
		#          [new subject] 
		
		foreach my $r (@REV)
		{
			my $SUBJECT = Swignition::RDFModel::Subject->new($current_object_resource);
			my $VALUE   = Swignition::RDFModel::Value->new($new_subject, 1);
			$SUBJECT->addVal($r, $VALUE);
			$this->mergeSubject($SUBJECT);
			$activity++;
		}
	}
	
	# If however [current object resource] was set to null, but there are 
	# predicates present, then they must be stored as [incomplete triple]s, 
	# pending the discovery of a subject that can be used as the object. Also, 
	# [current object resource] should be set to a newly created [bnode]
	elsif ((scalar @REL) || (scalar @REV))
	{
		# Predicates for [incomplete triple]s can be set by using one or
		# both of the @rel and @rev attributes:
		#
		#    * If present, @rel must contain one or more URIs, obtained 
		#      according to the section on CURIE and URI Processing each
		#      of which is added to the [local list of incomplete triples]
		#      as follows:
		#
		#      predicate
		#          full URI 
		#      direction
		#          forward 
		
		foreach my $r (@REL)
		{
			push @$local_incomplete_triples, {
				predicate => $r,
				direction => 'forward'
			};
		}
		
		#    * If present, @rev must contain one or more URIs, obtained
		#      according to the section on CURIE and URI Processing, each
		#      of which is added to the [local list of incomplete triples]
		#      as follows:
		#
		#      predicate
		#          full URI 
		#      direction
		#          reverse 
		
		foreach my $r (@REV)
		{
			push @$local_incomplete_triples, {
				predicate => $r,
				direction => 'reverse'
			};
		}
		
		$current_object_resource = Swignition::GenericParser::Utils::BNodeURI;
	}
	
	# The next step of the iteration is to establish any [current object
	# literal	
	my $datatype = -1;
	if (defined $current_element->getAttributeNode('datatype'))
		{ $datatype = curie($this, $current_element->getAttribute('datatype'), $current_element); }
		
	# Predicates for the [current object literal] can be set by using
	# @property. If present, one or more URIs are obtained according
	# to the section on CURIE and URI Processing and then the actual
	# literal value is obtained as follows:
	
	my $VALUE;
	
	# as a [ plain literal ] if: 
	#
	# @content is present; 
	if (defined $current_element->getAttributeNode('content'))
	{
		my $X = ms $current_element->getAttribute('content'), $current_element;
		$VALUE = Swignition::RDFModel::Value->new($X);
		$VALUE->setDatatype($datatype)
			if ($datatype != -1);
	}
	
	# or all children of the [current element] are text nodes;
	# or there are no child nodes;
	# or the body of the [ current element ] does have non-text
	#    child nodes but @datatype is present, with an empty value. 
	elsif ((!$current_element->getElementsByTagName('*')) 
	||     ($datatype eq ''))
	{
		$VALUE = Swignition::RDFModel::Value->new(
			Swignition::GenericParser::Utils::STRINGIFY($current_element,
				{'rdfa-stringify' => $this->{options}->{rdfa_strings}})
		);
	}

	# Additionally, if there is a value for [current language] then
	# the value of the [plain literal] should include this language
	# information, as described in [RDF-CONCEPTS]. The actual literal
	# is either the value of @content (if present) or a string created
	# by concatenating the text content of each of the descendant
	# elements of the [current element] in document order. 
	
	# as an [XML literal] if:
	# 
	#    * the [current element] has any child nodes that are not simply
	#      text nodes, and @datatype is not present, or is present, but
	#      is set to rdf:XMLLiteral.
	#
	# The value of the [XML literal] is a string created by serializing
	# to text, all nodes that are descendants of the [current element],
	# i.e., not including the element itself, and giving it a datatype of
	# rdf:XMLLiteral.
	elsif ($datatype eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral'
	|| ($datatype==-1 && $current_element->getElementsByTagName('*')->[0]))
	{
		$datatype = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral';
		$VALUE = Swignition::RDFModel::Value->new(
			Swignition::GenericParser::Utils::XML_STRINGIFY($current_element));
		$VALUE->setDatatype($datatype);
	}

	# TOBY: a tiny extension to RDFa. If datatype is equal to
	# <http://purl.org/uF/excerpt#value> then perform Microformat-style
	# value excerption.
	elsif ($datatype =~ m{^http://purl.org/uF/excerpt#([A-Za-z0-9\._-]+)$})
	{
		$VALUE = Swignition::RDFModel::Value->new(
			Swignition::GenericParser::Utils::STRINGIFY($current_element, {
				'excerpt-class'  => $1,
				'abbr-pattern'   => 1
			}));
	}
	
	# as a [typed literal] if:
	#
	#     * @datatype is present, and does not have an empty value.
	#
	# The actual literal is either the value of @content (if present)
	# or a string created by concatenating the value of all descendant
	# text nodes, of the [current element] in turn. The final string
	# includes the datatype URI, as described in [RDF-CONCEPTS], which
	# will have been obtained according to the section on CURIE and URI
	# Processing.
	elsif ($datatype != -1)
	{
		$VALUE = Swignition::RDFModel::Value->new(
			Swignition::GenericParser::Utils::STRINGIFY($current_element,
				{'rdfa-stringify' => $this->{options}->{rdfa_strings}})
		);
		$VALUE->setDatatype($datatype);
	}
	
	else
	{
		die("How did we get here??\n");
	}

	# The [current object literal] is then used with each predicate to
	# generate a triple as follows:
	# 
	# subject
	#     [new subject] 
	# predicate
	#     full URI 
	# object
	#     [current object literal] 
	my $prop = $current_element->getAttribute('property');
	$prop =~ s/(^\s+|\s+$)//g;
	$prop =~ s/\s+/ /g;
	my @prop = split / /, $prop;
	my $SUBJECT = Swignition::RDFModel::Subject->new($new_subject);
	foreach my $p (@prop)
	{	
		my $property = curie($this, $p, $current_element);
		next unless (length $property);
		$SUBJECT->addVal($property, $VALUE);
		$activity++;

		# Once the triple has been created, if the [datatype] of the
		# [current object literal] is rdf:XMLLiteral, then the [recurse]
		# flag is set to false.			
		$recurse = 0
			if ($datatype eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral');
	}		
	$this->mergeSubject($SUBJECT);
	
	# If the [recurse] flag is 'true', all elements that are children of the 
	# [current element] are processed using the rules described here, using a 
	# new [evaluation context], initialized as follows
	my $flag = 0;
	if ($recurse)
	{
		foreach my $kid ($current_element->childNodes)
		{
			# If the [skip element] flag is 'true' then the new [evaluation context]
			# is a copy of the current context that was passed in to this level of
			# processing, with the [language] and [list of URI mappings] values
			# replaced with the local values; 
			if ($skip_element)
			{
				$flag = consume(
					$this,
					$kid,
					$base,
					$parent_subject,
					$parent_object,
					$uri_mappings,
					$incomplete_triples,
					$language
				) || $flag;
			}
			
			# Otherwise, the values are: 
			else
			{
				$flag = consume(
					$this,
					$kid,
					$base,
					$new_subject,
					(defined $current_object_resource ? $current_object_resource : ($new_subject ? $new_subject : $parent_subject)),
					$local_uri_mappings,
					$local_incomplete_triples,
					$current_language
				) || $flag;
			}
		}	
	}
	
#	# If the [skip element] flag is 'false', and either: the previous step
#	# resulted in a 'true' flag, or [new subject] was set to a non-null and
#	# non-bnode value, then any [incomplete triple]s within the current context
#	# should be completed:
#	if (!$skip_element && ($flag || ((defined $new_subject) && ($new_subject !~ /^bnodeXXX:/))))
#	{

	if (!$skip_element && defined $new_subject)
	{
		# Loop through list of incomplete triples...
		foreach my $it (@$incomplete_triples)
		{
			my $direction = $it->{direction};
			my $predicate = $it->{predicate};
			
			if ($direction eq 'forward')
			{
				my $SUBJECT = Swignition::RDFModel::Subject->new($parent_subject);
				my $VALUE   = Swignition::RDFModel::Value->new($new_subject, 1);
				$SUBJECT->addVal($predicate, $VALUE);
				$this->mergeSubject($SUBJECT);
				$activity++;
			}
			else
			{
				my $SUBJECT = Swignition::RDFModel::Subject->new($new_subject);
				my $VALUE   = Swignition::RDFModel::Value->new($parent_subject, 1);
				$SUBJECT->addVal($predicate, $VALUE);
				$this->mergeSubject($SUBJECT);
				$activity++;
			}
		}
	}

	return 1 if ($activity || $new_subject || $flag);
	return 0;
}


sub curie
{
	my $page  = shift;
	my $str   = shift;
	my $dom   = shift;
	my $xpath = $dom->getAttribute('_xpath') if ($dom);
	my $safe  = 0;
	
	# Horrible syntax for weirdo blank node.
	if ($str eq '_:' || $str eq '[_:]')
	{
		return 'bnode:///iHateThisSyntax';
	}
	
	$str = 'xhv'.$str if ($str =~ /^\:/);
	
	# Cope with safe CURIEs.
	if ($str =~ /^\[(.*)\]$/)
	{
		$str  = $1;
		$safe = 1;
	}
	
	# If the string matches CURIE syntax, then resolve the CURIE.
	if ($str =~ /^([a-z_][^\s\:]*)\:(.*)$/i)
	{
		my $pfx = $1;
		my $sfx = $2;
		my $ns  = Swignition::Namespace::bestMatch($pfx, $xpath, @{$page->{Namespaces}});
		
		return $page->uri( $ns->{uri} . $sfx )
			if ($ns);
			
		return 'http://invalid.invalid/ns#'.$sfx
			if ($safe);
	}
	
	# If it wasn't a safe CURIE, then fall back to a URI.
	if (!$safe)
	{
		return $page->uri($str, {'require-absolute'=>1,'element'=>$dom});
	}
	
	return undef;
}


sub uriOrSafeCurie
{
	my $page  = shift;
	my $str   = shift;
	my $dom   = shift;
	my $xpath = $dom->getAttribute('_xpath') if ($dom);
	my $safe  = 0;
	
	# Horrible syntax for weirdo blank node.
	if ($str eq '_:' || $str eq '[_:]')
	{
		return 'bnode:///iHateThisSyntax';
	}

	$str = 'xhv'.$str if ($str =~ /^\:/);
	
	# Cope with safe CURIEs.
	if ($str =~ /^\[(.*)\]$/)
	{
		$str  = $1;
		$safe = 1;
	}
	
	# Only safe CURIEs allowed
	if ($safe)
	{
		# If the string matches CURIE syntax, then resolve the CURIE.
		if ($str =~ /^([a-z_][^\s\:]*)\:(.*)$/i)
		{
			my $pfx = $1;
			my $sfx = $2;
			my $ns  = Swignition::Namespace::bestMatch($pfx, $xpath, @{$page->{Namespaces}});
			
			return $page->uri( $ns->{uri} . $sfx )
				if ($ns);
			
			return 'http://invalid.invalid/ns#'.$sfx
				if ($safe);
		}
	}
	
	# If it wasn't a safe CURIE, then fall back to a URI.
	else
	{
		return $page->uri($str, {'element'=>$dom});
	}
	
	return undef;
}


sub reservedWordOrCurie
{
	my $page  = shift;
	my $str   = shift;
	my $dom   = shift;
	my $xpath = $dom->getAttribute('_xpath') if ($dom);
	my $safe  = 0;
	
	# Horrible syntax for weirdo blank node.
	if ($str eq '_:' || $str eq '[_:]')
	{
		return 'bnode:///iHateThisSyntax';
	}
	
	# From HTML 4.01 and (in all-caps) HTML 3.2 specs 
	if ($str =~ /^\:?( Alternate | Stylesheet | Start | Next | Prev |
		Contents | Index | Glossary | Copyright | Chapter | Section |
		Subsection | Appendix | Help | Bookmark | PREVIOUS | SEARCH |
		MADE | TOP )$/ix)
	{
		$str = lc("[xhv:$1]");
	}
	# And the HTML 5 spec.
	# 2008-07-20 : removed 'contact' as it's gone from HTML 5 draft.
	elsif ($str =~ /^\:?( alternate | archives | author | bookmark |
		external | feed | first | help | icon | index | last | license | next |
		nofollow | noreferrer | pingback | prefetch | prev | search |
		stylesheet | sidebar | tag | up )$/ix)
	{
		$str = lc("[xhv:$1]");
	}
	# Selected values from XHTML 2
	elsif ($str =~ /^\:?( cite | meta | p3pv1 | role )$/ix)
	{
		$str = lc("[xhv:$1]");
	}
	# GRDDL
	elsif (lc($str) eq 'transformation')
		{ return 'http://www.w3.org/2003/g/data-view#transformation'; }
	elsif (lc($str) eq 'profiletransformation')
		{ return 'http://www.w3.org/2003/g/data-view#profileTransformation'; }
	elsif (lc($str) eq 'namespacetransformation')
		{ return 'http://www.w3.org/2003/g/data-view#namespaceTransformation'; }

	# Cope with safe CURIEs.
	if ($str =~ /^\[(.*)\]$/)
	{
		$str  = $1;
		$safe = 1;
	}
	
	# If the string matches CURIE syntax, then resolve the CURIE.
	if ($str =~ /^([a-z_][^\s\:]*)\:(.*)$/i)
	{
		my $pfx = $1;
		my $sfx = $2;
		my $ns  = Swignition::Namespace::bestMatch($pfx, $xpath, @{$page->{Namespaces}});
		
		return $page->uri( $ns->{uri} . $sfx )
			if ($ns);
			
		return 'http://invalid.invalid/ns#'.$sfx
			if ($safe);
	}
	
	# If it wasn't a safe CURIE, then fall back to a URI.
	if (!$safe)
	{
		return $page->uri($str, {'require-absolute'=>1,'element'=>$dom});
	}
	
	return undef;
}


1;
